package binding

import (
	"io"
	"mime/multipart"
	"net/http"
	"time"

	"gitee.com/go-errors/errors"
	"github.com/valyala/fastjson"
)

func jsonSource(req *http.Request) (Source, error) {
	data, err := io.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}
	fjv, err := fastjson.ParseBytes(data)
	if err != nil {
		return nil, err
	}
	return fjValuer{fjv: fjv}, nil
}

type fjValuer struct {
	fjv *fastjson.Value
}

func (ver fjValuer) Get(key string) (v Source, err error) {
	if fjv := ver.fjv.Get(key); fjv == nil {
		err = ErrNoValue
	} else {
		v = fjValuer{fjv: fjv}
	}
	return
}

func (ver fjValuer) Map(iter func(key string, valuer Source)) {
	if obj, err := ver.fjv.Object(); err == nil {
		obj.Visit(func(key []byte, v *fastjson.Value) { iter(b2s(key), fjValuer{fjv: v}) })
	}
}

func (ver fjValuer) Array(iter func(index int, valuer Source) bool, beforeFs ...func(size int)) {
	if fvs, err := ver.fjv.Array(); err == nil {
		l := len(fvs)
		for _, before := range beforeFs {
			before(l)
		}
		for i, fjv := range fvs {
			if !iter(i, fjValuer{fjv: fjv}) {
				break
			}
		}
	}
}

func (ver fjValuer) String() (s string, err error) {
	var v []byte
	v, err = ver.fjv.StringBytes()
	s = b2s(v)
	return
}

func (ver fjValuer) Int() (i int64, err error) {
	return ver.fjv.Int64()
}

func (ver fjValuer) Uint() (u uint64, err error) {
	return ver.fjv.Uint64()
}

func (ver fjValuer) Float() (f float64, err error) {
	return ver.fjv.Float64()
}

func (ver fjValuer) Bool() (b bool, err error) {
	return ver.fjv.Bool()
}

func (ver fjValuer) Time() (t time.Time, err error) {
	var s string
	if s, err = ver.String(); err != nil {
		return
	}
	var ex error
	for _, layout := range timeLayouts {
		if t, ex = time.Parse(layout, s); ex != nil {
			err = errors.Multi(err, ex)
		} else {
			err = nil
			return
		}
	}
	return
}

func (ver fjValuer) Duration() (d time.Duration, err error) {
	if s, err := ver.String(); err != nil {
		return d, err
	} else {
		return time.ParseDuration(s)
	}
}

func (ver fjValuer) File() (f *multipart.FileHeader, err error) {
	return nil, ErrNoValue
}

func (ver fjValuer) Files() (f []*multipart.FileHeader, err error) {
	return nil, ErrNoValue
}

var _ Source = fjValuer{}
